Définition & déclaration ************************ Le langage C++ s'est construit au fil du temps et pas toujours dans un soucis d'harmonisation. Ainsi ce chapitre qui aborde les déclarations et les définitions en C++ amène tout un lot de cas particuliers et de dérogations liées à certains mots-clefs. Nous avons volontairement choisi comme pédagogie de mettre l'accent sur quelques grands principes. Pour cela, nous nous plaçons dans un cadre idéal d'un programme comportant **un seul fichier source sans aucune inclusion**. Principe général ================ Dans cette page, nous choisissons de désigner par le terme *entité* : une variable, une fonction, une structure... Définition et déclaration en C++ -------------------------------- .. warning:: Dans l'univers du C++, déclaration et définition sont deux notions proches mais cependant différentes. Faîtes très attention, car dans d'autres langages, ces deux termes sont souvent utilisés de manière équivalente. D'après la `terminologie du langage C++ `_, une **déclaration** sert à : * Introduire ou réintroduire une entité dans un programme en rappelant son type. Une `définition `_ correspond à : * Une déclaration * Suivie de suffisamment d'informations pour que le compilateur puisse créer cette entité. .. note:: Vous remarquez qu'une définition est aussi une déclaration ! On peut aussi voir une déclaration comme une définition tronquée. Voici une règle commune à de nombreux langages : .. panels:: :column: col-lg-10 p-2 **Règle - Declaration-before-use** : vous devez déclarer une entité avant de l'utiliser Dans le langage C++, le principe intitulé `One Definition Rule `_ s'applique : .. panels:: :column: col-lg-10 p-2 **Règle ODR - One Definition Rule** : dans un fichier source, une seule définition est autorisée par entité .. note:: Durant la compilation d'un fichier source, si le compilateur trouve deux définitions, il émet une erreur de redéfinition. .. note:: Imposer une seule définition par entité tient du bon sens. En effet, comment devrait réagir le compilateur s'il trouvait dans le même fichier source deux fonctions *toto()* effectuant des traitements différents ! .. note:: La règle ODR concerne les définitions, cependant, il n'y a pas de limite sur le nombre de déclarations associées à une même entité tant qu'elles restent identiques (non contradictoires). Exercice -------- .. quiz:: IntroDefDecla :title: Définition & Déclaration **Dans le cas d'un unique fichier source présent dans le projet**, indiquez si les affirmations suivantes sont vraies ou fausses : #) :quiz:`{"type":"TF","answer":"F"}` En C++, définition et déclaration signifient la même chose. #) :quiz:`{"type":"TF","answer":"F"}` La règle ODR indique qu'il faut que chaque entité soit définie au moins une fois durant la compilation. #) :quiz:`{"type":"TF","answer":"T"}` Une déclaration introduit ou réintroduit un nom et un type. #) :quiz:`{"type":"TF","answer":"T"}` On peut trouver plusieurs déclarations d'une même entité dans un fichier source. #) :quiz:`{"type":"TF","answer":"F"}` Si une déclaration est présente, la définition devient optionnelle. #) :quiz:`{"type":"TF","answer":"F"}` On peut utiliser un nom n'importe où dans le fichier source sans risque d'erreur. Les variables ============= Vous pouvez retrouver une `présentation sur les variables `_ dans la référence sur le C++. Syntaxe ------- .. panels:: :column: col-lg-10 p-2 **Définition d'une variable :** #) Type Nom; // sans initialisation #) Type Nom = ValeurInitiale; // avec initialisation Définir une variable sans l'initialiser est risquée et doit être réservée à de très rares occasions. .. note:: Les syntaxes à base d'accolades : *int a = {40};* ou *int a{40};* ou *int a = {};* ou *int a{};* sont hors programme. Exemple ------- .. code-block:: cpp int unEntier; // définition d'une variable de type entier sans initialisation double unNombreFloatant = 3.0; // définition de la variable unNombreFloatant de type double avec son initialisation .. warning:: La déclaration de variable est hors programme Initialisation des variables par défaut --------------------------------------- Pour commencer, nous abandonnons une croyance assez tenace chez les élèves : .. # define a hard line break for HTML .. |br| raw:: html
.. panels:: :column: col-lg-10 p-2 NON, les variables ne sont pas initialisées par défaut en C++. |br| NON, les variables numériques ne sont pas initialisées à 0 en C++. Certains d'entre vous testerons cette affirmation à travers le programme suivant : le but est de créer un entier sans l'initialiser et d'afficher ensuite sa valeur à l'écran, ceci plusieurs fois. .. code-block:: cpp #include int main() { for (int i = 0; i < 100 ; i++) { int a; std::cout<> 0 0 0 0 0 0 0 ... .. warning:: Sur 100 essais, vous obtiendrez probablement toujours la valeur 0. Mensonge, il y aurait une initialisation masquée !! Cette fausse impression est légitime, mais il y a une raison à cela. En effet, lors du lancement de votre programme, son espace mémoire est mis à zéro afin d'effacer toute trace d'informations provenant d'autres programmes. Ainsi, en début de programme, statistiquement, les variables numériques ont de forte chance d'occuper un espace mémoire contenant la valeur 0. Mais c'est un coup de chance si l'on peut dire ! Après un certain temps, le programme alloue de nouvelles variables en mémoire en lieu et place d'anciennes variables et là, les valeurs ne sont pas forcément nulles ! En transformant le programme précédent pour qu'il modifie la case mémoire de la variable *a*, on obtient un résultat beaucoup plus amusant : .. code-block:: cpp int main() { for (int i = 0; i < 100 ; i++) { int a; a += 1; std::cout<> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35... Cette exemple montre que la variable *a* lorsqu’elle est créée réutilise la case mémoire de la précédente variable *a*. .. warning:: L'utilisation d'une variable non initialisée ne produit pas de message d'erreur. Il est possible de forcer le compilateur à déclencher une erreur en utilisant des paramètres de compilation supplémentaires **-Wall -Werror**. .. panels:: :column: col-lg-10 p-2 Convention : tant qu'une variable n'est pas initialisée, il ne faut pas l'utiliser car son contenu est indéterminée. Masquage -------- Pour une variable, la règle ODR s'applique à l'intérieur du bloc d'accolades où elle a été créée. Cependant, dans un sous-bloc d'accolades, vous pouvez redéfinir une variable portant le même nom qu'une variable précédente. Dans ce cas, la variable dans ce sous-bloc masque la précédente : .. code-block:: cpp #include int main() { int a = 2; { int a = 3; // cette nouvelle variable a masque celle du bloc supérieur std::cout<> 3 **Cette situation est à éviter** car elle nuit fortement à la lisibilité du programme. Exemples d'erreurs ------------------ L'utilisation d'une variable avant toute définition produit une erreur de la forme : "le nom X n'est pas déclaré". Voici un exemple : .. code-block:: cpp #include int main() { std::cout << a; return 0; } Le redéfinition d'une variable, même à l'identique, produit une erreur de compilation de la forme : "erreur de redéclaration". Voici un exemple : .. code-block:: cpp int main() { int a = 2; int a = 2; return 0; } .. panels:: Exercice 1 .. code-block:: cpp :linenos: a = 0; int a; --- Exercice 2 .. code-block:: cpp :linenos: int a = 2; a = 4; --- Exercice 3 .. code-block:: cpp :linenos: int a; int b = c; int c; --- Exercice 4 .. code-block:: cpp :linenos: int a = 2; int i = 3; int c; int d = a + j; .. quiz:: Reglesdutilisation :title: Règles d'utilisation Le compilateur parcourt le code source de haut en bas. Dès qu'il rencontre un problème : identificateur inconnu, variable non initialisée... il s'arrête et envoie un message d'erreur. Pour chaque exemple, indiquez la ligne où se trouve l'erreur ou 0 si tout est correct : .. csv-table:: :delim: ! Exercice 1 :quiz:`{"type":"FB","answer":"1"}` ! Exercice 2 :quiz:`{"type":"FB","answer":"0"}` Exercice 3 :quiz:`{"type":"FB","answer":"2"}` ! Exercice 4 :quiz:`{"type":"FB","answer":"4"}` .. quiz:: IntroDefDeclaBis :title: Définition de variables Indiquez si les affirmations suivantes sont vraies ou fausses : #) :quiz:`{"type":"TF","answer":"T"}` La définition d'une variable donne son nom et son type. #) :quiz:`{"type":"TF","answer":"T"}` Le compilateur C++ accepte d'utiliser une variable non initialisée. #) :quiz:`{"type":"TF","answer":"F"}` En C++, les numériques sont automatiquement initialisées à la valeur 0. #) :quiz:`{"type":"TF","answer":"F"}` Deux définitions du même nom sont possibles si les types sont identiques. #) :quiz:`{"type":"TF","answer":"T"}` On peut utiliser une variable après sa définition. Les fonctions ============= Syntaxe ------- La `spécification du C++ sur les fonctions `_ nous informe que : * La **déclaration d'une fonction** permet d'indiquer son nom et son type (type de retour et type des arguments) * La **définition d'une fonction** est une déclaration suivie d'un **corps de fonction** (function body) délimité par une paire d'accolades { } et contenant l'ensemble des traitements effectués par cette fonction. .. panels:: :column: col-lg-10 p-2 **Déclaration d'une fonction :** TypeRetour NomFonction(Type1 NomParametre1, Type2 NomParamètre2); .. panels:: :column: col-lg-10 p-2 **Définition d'une fonction :** | TypeRetour NomFonction(Type1 NomParametre1, Type2 NomParamètre2) | { | // instructions | } .. note:: Les fonctions ne retournant pas de valeur doivent retourner le type void. Forward declaration ------------------- Si on ne disposait pas des déclarations, cela obligerait les programmeurs à organiser les fonctions dans un certain ordre dans le code. Par exemple, si une fonction *A()* appelait une fonction *B()*, la définition de la fonction *B()* devrait alors être placée avant la définition de la fonction *A()* dans le source. Heureusement, l'utilisation des déclarations permet de ne plus avoir à se soucier de l'ordre dans lequel les définitions des fonctions *A()* et *B()* doivent se trouver. En effet, il suffit d'écrire une déclaration (**Forward declaration**) de la fonction *B()* avant toute utilisation de cette fonction dans le programme. Voici un exemple : .. code-block:: cpp void B(); // Forward declaration de la fonction B void A() // définition de la fonction A { B(); // la fonction B peut être appelée car elle a été déclarée auparavant } void B() // la définition de la fonction B est placée après celle de A grâce au mécanisme de Forward Declaration { ... } Exemples d'erreur ----------------- L'utilisation d'une fonction avant toute définition ou déclaration produit une erreur de la forme : "le nom n'est pas déclaré". Voici un exemple : .. code-block:: cpp int main() { F(); => ERREUR : "le nom F n'est pas déclaré". return 0; } void F() {} Le redéfinition d'une fonction, même à l'identique, produit une erreur de compilation de la forme : "Le nom a déjà été défini". Voici un exemple : .. code-block:: cpp void F() {} void F() {} => ERREUR : Le nom F a déjà été défini int main() { F(); return 0; } La déclaration d'une fonction sans définition produit une erreur de la forme "aucune définition trouvée" : .. code-block:: cpp void F(); int main() { F(); return 0; } .. quiz:: IntroDefDeclaFNT :title: Définition et déclaration de fonctions Indiquez si les affirmations suivantes sont vraies ou fausses : #) :quiz:`{"type":"TF","answer":"F"}` En C++, définition et déclaration de fonctions signifient la même chose. #) :quiz:`{"type":"TF","answer":"F"}` Si la fonction ne retourne pas de valeur, son type de retour est absent. #) :quiz:`{"type":"TF","answer":"T"}` Une définition de fonction doit inclure un corps de fonction. #) :quiz:`{"type":"TF","answer":"F"}` Un corps de fonction est délimité par une paire de crochets. #) :quiz:`{"type":"TF","answer":"F"}` Un corps de fonction se termine par un point virgule ; #) :quiz:`{"type":"TF","answer":"T"}` Un appel de fonction sans déclaration préalable déclenche une erreur. #) :quiz:`{"type":"TF","answer":"F"}` La syntaxe d'une déclaration de fonction se termine par une parenthèse ) #) :quiz:`{"type":"TF","answer":"F"}` Si le compilateur ne trouve aucune définition, il n'émet pas d'erreur. #) :quiz:`{"type":"TF","answer":"F"}` Si le compilateur trouve deux définitions identiques dans le même fichier, il n'émet pas d'erreur. Les structures ============== Nous rappelons qu'un type structure désigne un type composé. Il s'agit d'une **classe** dont tous les membres sont publics par défaut. .. warning:: Attention, définir un type structure nommée *S* et définir une variable de type *S* sont deux choses différentes. Dans le premier cas, on définit une classe dans l'autre on instancie un objet. Syntaxe ------- On trouve : * La **déclaration d'un type structure** permet de déclarer son nom et son type (struct) * La **définition d'un type structure** inclut la déclaration suivie d'une liste de variables/fonctions présentes dans la structure. Cette liste est délimitée par une paire d'accolades et se termine par un ; .. panels:: :column: col-lg-10 p-2 **Déclaration d'une structure :** struct Nom; .. panels:: :column: col-lg-10 p-2 **Définition d'une structure :** | struct Nom | { | Type1 var1; | Type2 fnt(Type3 var2) {...}; | ... | }; .. warning:: Après une déclaration, la structure peut être utilisée de manière très limitée. En effet, aucune information n'est donnée sur les membres internes et il n'est donc pas possible d'y accéder. On peut cependant créer des pointeurs vers cette structure. Exemples d'erreur ----------------- Si nous insérons deux fois une définition dans un même fichier, ceci produit une erreur de redéfinition : .. code-block:: cpp struct A { int a; }; struct A { int a; }; // erreur : définition antérieure de struct A int main() { } .. quiz:: IntroDefDeclaStuct :title: Définition et déclaration des types structures Indiquez si les affirmations suivantes sont vraies ou fausses : #) :quiz:`{"type":"TF","answer":"T"}` Un type structure est un type. #) :quiz:`{"type":"TF","answer":"F"}` La syntaxe de la définition d'un type structure se termine par une accolade } #) :quiz:`{"type":"TF","answer":"F"}` Dans un même fichier, on peut définir plusieurs fois le même type structure.